/*******************************************************************************************
Copyright (C) 2016 Mestrelab Research S.L. All rights reserved.

This file is part of the Mnova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*******************************************************************************************/

/*globals mainWindow, nmr, serialization, settings, Application, BinaryStream, Dir, Document, File, FileDialog, MnUi, NMRSpectrum, ProgressDialog, Str, TextStream*/
/*jslint plusplus: true, bitwise: true, indent: 4*/

function vaxBytesToFloat32(aBytes) {
	"use strict";
	var b1 = aBytes.at(1),
		b2 = aBytes.at(0),
		b3 = aBytes.at(3),
		b4 = aBytes.at(2),

		S = (b1 & 0x80) >> 7,
		E = ((b1 & 0x7f) << 1) + ((b2 & 0x80) >> 7),
		F = ((b2 & 0x7f) << 16) + (b3 << 8) + b4,

		M = 0.5 + F / 16777216.0;

	if (E > 0) {
		return (S ? -1 : 1) * M * Math.pow(2.0, E - 128);
	}
	if (E === 0 && S === 0) {
		return 0.0;
	}
	if (E === 0 && S === 1) {
		return -1.0;
	}

	return 0.0;
}

function importPhilipsSdat() {
	"use strict";

	function getPhilipsSpar(aParamName, aParams) {
		var nlPos,
			parPos = aParams.indexOf(aParamName);
		if (parPos === -1) {
			return undefined;
		}

		nlPos = aParams.indexOf("\n", parPos);
		if (nlPos === -1) {
			nlPos = aParams.length;
		}

		return aParams.substring(parPos + aParamName.length + 2, nlPos).replace(/^ */, "");
	}

	var fi, sdatName, sparName, fspar, fsdat, sspar, params, ssdat, rows, npts, r, i, re, im, proc,
		mainDocument, frequency, nucleus, spectrum, realValues, imagValues, specSize, fromPPM, endPPM,
		dirSetKey = "importPhilipsSdat/Dir", progressBar, auxiliaryDocument, spectraArray, page, pages,
		openDir = settings.value(dirSetKey, Dir.home()), auxSparName, auxProgressValue, step, refFreq,
		fileNames = FileDialog.getOpenFileNames("Philips Achieva (*.SDAT)", "", openDir);

	if (!fileNames.length) {
		return;
	}
	serialization.fileHistory = false;

	//It creates a new document if the active document has already data
	mainDocument = undefined;
	if (Application.mainWindow.activeDocument !== undefined && Application.mainWindow.activeDocument.itemCount() === 0) {
		mainDocument = Application.mainWindow.activeDocument;
	} else if (mainDocument === undefined || !mainDocument.isValid() || mainDocument.itemCount() !== 0) {
		mainDocument = Application.mainWindow.newDocument();
		mainWindow.activeDocument = mainDocument;
	}

	//Create a progress bar in order to inform to the user about the state of the import process
	progressBar = new ProgressDialog();
	progressBar.minimum = 0;
	progressBar.maximum = 100;
	progressBar.value = 0;
	progressBar.visible = true;

	for (fi = 0; fi < fileNames.length && !progressBar.wasCanceled; fi++) {
		sdatName = fileNames[fi];
		sparName = sdatName.substring(0, sdatName.lastIndexOf(".")) + ".SPAR";
		auxSparName = sparName.substring(sdatName.lastIndexOf("/") + 1, sparName.length);
		progressBar.labelText = "Importing " + auxSparName + " file";
		progressBar.value = (fi / fileNames.length) * 100;
		progressBar.update();

		//We work in a single document but each SDAT will be in its onw page
		if (mainDocument.curPage().itemCount() !== 0) {
			mainDocument.newPage();
		}

		fspar = new File(sparName);
		fspar.open(File.ReadOnly);
		fsdat = new File(sdatName);
		fsdat.open(File.ReadOnly);

		if (!fi) {
			settings.setValue(dirSetKey, fspar.absDirPath);
		}

		sspar = new TextStream(fspar);
		params = sspar.readAll();
		fspar.close();

		ssdat = new BinaryStream(fsdat);
		ssdat.endianness = BinaryStream.eLittle;
		rows = Number(getPhilipsSpar("rows", params));
		npts = Number(getPhilipsSpar("dim1_pnts", params));
		for (r = 0; r < rows && !progressBar.wasCanceled; r++) {

			//Get the spectrum parameters only once
			if (r === 0) {

				//Get needed parameter to create the spectrum. To guess ppm values we use the formula  1 / (dim1_step × synthesizer_frequency)
				specSize = npts;
				frequency =  Number(getPhilipsSpar("synthesizer_frequency", params)) / 1e6;
				step = Number(getPhilipsSpar("dim1_step", params));
				nucleus = getPhilipsSpar("nucleus", params);

				//If for that reason these values are missing in header block, we use the default values written at TASCIINMRConverter
				//TASCIINMRConverter looks for entry "frequency" in the header. If there is no entry (but it's - it says "frequency undefined") it sets to 400.
				//Otherwise somewhere else it set it to default 300 stored at NMR General.cpp essentials::defMHz = 300.0;
				if (frequency === "undefined" || frequency === undefined) {
					frequency = 400;
				} else if (isNaN(frequency)) {
					frequency = 300;
				}

				//Ppm calculation by using the reference frequency
				//The ppm scale has to be calculated against reference frequency, not the spectrometer frequency.
				refFreq = frequency - 1e-6 / (2.0 * step);
				fromPPM = 0;
				endPPM = (1 / (step * refFreq));

				//Auxiliary document to build the arrayed NMRSpectrum
				if (rows > 1) {

					//It creates the auxiliary document in order to create the molecule image
					//Calling this constructor the document will be hidden unless we call to the method mainWindow.addDocument
					auxiliaryDocument = new Document();

					//It locks the document to ensure all pointers like mainWindow.activeDocument and activeMolecule() points to the hidden document
					Application.lockDocument(auxiliaryDocument);

					//Get current value for adding and initialize the array
					spectraArray = [];
					auxProgressValue = progressBar.value;
				}
			}

			//The final spectrum will be an arrayed NMRSpectrum object
			if (rows > 1) {

				//Update progress bar bearing in mind the stack
				progressBar.labelText = "Stacking for " + auxSparName + " file (" + (r + 1) + " of " + rows + ")";
				if (progressBar.value > 0) {
					progressBar.value = auxProgressValue + (((1 / fileNames.length) * 100) * ((r + 1) / rows));
				} else {
					progressBar.value = ((1 / fileNames.length) * 100) * ((r + 1) / rows);
				}
				progressBar.update();

				//Create a new page in hiden document and import the spectrum
				auxiliaryDocument.newPage();
				mainWindow.activeDocument = auxiliaryDocument;
				spectrum = importPhilipsSdat.createSpectrum(specSize, nucleus, frequency, fromPPM, endPPM, sdatName);
				spectraArray.push(spectrum);

			} else {
				//Create the spectrum object
				spectrum = importPhilipsSdat.createSpectrum(specSize, nucleus, frequency, fromPPM, endPPM, sdatName);
			}

			//Get the spectrum data in time domain
			realValues = [];
			imagValues = [];
			for (i = 0; i < npts && !progressBar.wasCanceled; i++) {
				re = vaxBytesToFloat32(ssdat.readBytes(4));
				im = vaxBytesToFloat32(ssdat.readBytes(4));
				realValues.push(re);
				imagValues.push(im);
			}

			if (spectrum !== undefined && spectrum.isValid()) {

				//Fill spectrum using fid method because we are woriking in the time domain
				nmr.beginFidModification(spectrum);
				spectrum.fidSetReal("all", realValues);
				spectrum.fidSetImag("all", imagValues);
				nmr.endFidModification(spectrum);
				mainDocument.update();
				spectrum.fitToHeight();
				spectrum.update();

				//Apply processing algorithms
				proc = spectrum.proc;
				proc.setParameter("FT[1].Quadrature", true);
				proc.setParameter("PC.Method", "Global");
				spectrum.process(proc);
				spectrum.update();
			}

			//The last iteration we create the arrayed NMRSpectrum using the spectra within the auxiliary document
			if (rows > 1) {
				if (r === rows - 1) {

					//It unlocks the auxiliary document in order to restore the pointers
					Application.unlockDocument();
					mainWindow.activeDocument = mainDocument;

					//Create the arrayed item and manage the document pages
					pages = mainDocument.pageCount();
					page = mainDocument.curPage();

					//It crates the arrayed items
					nmr.createArrayedItem(spectraArray);

					//It checks and deletes the previous page if it is blank.
					//This happens because the createArrayedItem method creates the item in a new page
					if (pages < mainDocument.pageCount()) {
						if (page.itemCount() === 0) {
							mainDocument.deletePages(page);
						}
					}

					//Destroy the hidden document
					auxiliaryDocument.destroy();
				}
			}
		}
	}
	fsdat.close();
	if (mainDocument !== undefined) {
		mainDocument.update();
	}

	//Finalize progressBar and manage the history
	serialization.fileHistory = true;
	progressBar.value = 100;
	progressBar.visible = false;
}

importPhilipsSdat.createSpectrum = function createSpectrum(aSize, aNuc, aMhz, aPpm1, aPpm2, aFileName) {
	"use strict";

	var spectrum;

	//Create spectrum object
	spectrum = new NMRSpectrum(
		{
			1: { nucleus: aNuc, frequency: aMhz, from_ppm: aPpm1, to_ppm: aPpm2, size: aSize },
			filename: aFileName,
			origin: 'SDAT and SPAR files',
			title: ''
		}
	);

	return spectrum;
};

if (this.MnUi && MnUi.scripts_nmr) {
	MnUi.scripts_nmr.scripts_nmr_ImportPhilipsAchieva = importPhilipsSdat;
}
